/* Process.cpp
Michael Zahniser, 12/21/09

Function definitions for the Process class.
*/

#include "Process.h"

#include <unistd.h>
#include <sys/wait.h>
#include <signal.h>
#include <cstdlib>
#ifndef __linux
	#include <sys/sysctl.h>
#endif
#include <sstream>
#include <iostream>

using std::string;
using std::vector;
using std::istringstream;
using std::cin;
using std::cerr;
using std::endl;
using std::exit;



Process::Process()
	: args(NULL), childProcess(0), stdinCopy(0)
{
}



Process::~Process()
{
	Kill();
}


	
void Process::Start(const string &command, char **argv)
{
	Kill();
	
	// Tokenize the command.
	string token;
	istringstream in(command);
	while(in >> token)
		tokens.push_back(token);
	
	if(argv)
		for(char **it = argv; *it; ++it)
			tokens.push_back(*it);
	
	args = new char *[tokens.size() + 1];
	char **arg = args;

	vector<string>::const_iterator it = tokens.begin();
	for( ; it != tokens.end(); ++it, ++arg)
		*arg = const_cast<char *>(it->c_str());
	*arg = NULL;
	
	Start();
}



// Kill the process.
void Process::Kill()
{
	if(childProcess > 0)
	{
		kill(childProcess, SIGTERM);
		wait(NULL);
	
		CleanUp();
	}
}



// Wait nicely for the process to finish up.
void Process::Wait()
{
	if(childProcess > 0)
	{
		wait(NULL);
	
		CleanUp();
	}
}



void Process::Start()
{
	// Create a pipe.
	if(pipe(pipe_fd))
		cerr << "Unable to create pipe." << endl;
	else
	{
		// Fork this process.
		childProcess = fork();
		
		if(childProcess < 0)
			cerr << "Unable to fork." << endl;
		else if(childProcess > 0)
			DoParent();
		else
			DoChild();
	}
}



void Process::CleanUp()
{
	childProcess = 0;
	
	// Restore the original STDIN.
	close(0);
	dup2(stdinCopy, 0);
	// We no longer need the copy of STDIN.
	close(stdinCopy);
	// Clear the error flags on cin.
	cin.clear();
#ifndef __linux
	// On Mac OS X, this seems to be necessary to get cin working again:
	cin.seekg(0);
#endif

	delete [] args;
	tokens.clear();
}



void Process::DoParent()
{
	// Make a copy of the original STDIN.
	stdinCopy = dup(0);
	// Pipe the input from the child into STDIN.
	dup2(pipe_fd[0], 0);
	
	// We've copied the end of the pipe that we need,
	// so close both ends, now.
	close(pipe_fd[0]);
	close(pipe_fd[1]);
}



void Process::DoChild()
{
	// Redirect both STDOUT and STRERR into the pipe.
	dup2(pipe_fd[1], 1);
	dup2(pipe_fd[1], 2);
	
	// We've copied the end of the pipe that we need,
	// so close both ends, now.
	close(pipe_fd[0]);
	close(pipe_fd[1]);

	// Run the process.
	execvp(args[0], args);
	
	// We should never get here; this program should _become_ scons.
	cerr << "Unable to launch process." << endl;
	exit(1);
}
